﻿namespace Legend.Collections
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading;
    using Legend.Threading;

    /// <summary>
    /// Provides helpers and extension methods for working with collections.
    /// </summary>
    public static partial class Collection
    {
        /// <summary>
        /// Gets a value indicating if the specified collection contains any
        /// value that matches the specified predicate.
        /// </summary>
        /// <typeparam name="T">The type of values in the collection.</typeparam>
        /// <param name="collection">The collection to find in.</param>
        /// <param name="predicate">A predicate function that returns true for any
        /// item that's a match.</param>
        /// <returns>True if a match was found in the collection.</returns>
        public static bool Contains<T>(this IEnumerable<T> collection, Func<T, bool> predicate)
        {
            Require.ThatArgument(predicate).Named("predicate").IsNotNull();

            foreach (var item in collection)
            {
                if (predicate(item))
                {
                    return true;
                }
            }

            return false;
        }

        /// <summary>
        /// Removes all items in the collection that the specified predicate selects.
        /// </summary>
        /// <typeparam name="T">The type of values in the collection.</typeparam>
        /// <param name="collection">The collection to remove from.</param>
        /// <param name="predicate">The predicate used to find values to remove.</param>
        public static void Remove<T>(this ICollection<T> collection, Func<T, bool> predicate)
        {
            Require.ThatArgument(predicate).Named("predicate").IsNotNull();
            
            var matches = 
                from item in collection
                where predicate(item)
                select item;

            foreach (var match in matches.ToList())
            {
                collection.Remove(match);
            }
        }

        /// <summary>
        /// Gets a collection containing the cross product of the collection.
        /// </summary>
        /// <typeparam name="T">The type of items in the collection.</typeparam>
        /// <param name="collection">The collection to get the crossproduct of.</param>
        /// <returns>A collection containing thee cross product of the original collection.</returns>
        public static IEnumerable<Tuple<T, T>> CrossProduct<T>(this IEnumerable<T> collection)
        {
            return
                from first in collection
                from second in collection
                select new Tuple<T, T>(first, second);
        }

        /// <summary>
        /// Gets a read only view over the specified dictionary.
        /// </summary>
        /// <param name="dictionary">The dictionary to get as read only.</param>
        /// <returns>An IDictionary(TKey, TValue) instance.</returns>
        public static IDictionary<TKey, TValue> AsReadOnly<TKey, TValue>(this IDictionary<TKey, TValue> dictionary)
        {
            Require.ThatArgument(dictionary).Named("dictionary").IsNotNull();

            return new ReadOnlyDictionary<TKey, TValue>(dictionary);
        }

        /// <summary>
        /// Returns a synchronized view of the specified collection.
        /// </summary>
        /// <typeparam name="T">The type of items in the collection.</typeparam>
        /// <param name="collection">The collection to make synchornized.</param>
        /// <returns>A synchronized view of the collection.</returns>
        public static ICollection<T> AsSynchronized<T>(this ICollection<T> collection)
        {
            Require.ThatArgument(collection).Named("collection").IsNotNull();

            var synchronizationManager = new ReaderWriterLockSlimAdapter(new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion));
                
            return new SynchronizedCollection<T>(
                collection, 
                synchronizationManager);
        }

        /// <summary>
        /// Gets the value by the specified key from the dictionary, if the value is not
        /// found in the value from the defaultValue function will be inserted and returned.
        /// </summary>
        /// <typeparam name="TKey">The type of the keys in the dictionary.</typeparam>
        /// <typeparam name="TValue">The type of the values in the dictionary.</typeparam>
        /// <param name="dictionary">The dictionary to get the value from.</param>
        /// <param name="key">The key of the value to get.</param>
        /// <param name="defaultValue">A function that returns the value that's inserted into
        /// the dictionary and returned if the key does not exist in the dictionary.</param>
        /// <returns>A value of the type TValue.</returns>
        public static TValue GetValue<TKey, TValue>(this IDictionary<TKey, TValue> dictionary, TKey key, Func<TValue> defaultValue)
        {
            Require.ThatArgument(dictionary).Named("dictionary").IsNotNull();
            Require.ThatArgument(key).Named("key").IsNotNull();
            Require.ThatArgument(defaultValue).Named("defaultValue").IsNotNull();
        
            TValue result;

            if (!dictionary.TryGetValue(key, out result))
            {
                result = defaultValue();
                dictionary.Add(key, result);
            }

            return result;
        }

        /// <summary>
        /// Creates a synchronized dictionary that wraps the specified dictionary.
        /// </summary>
        /// <typeparam name="TKey">The type of the keys in the dictionary.</typeparam>
        /// <typeparam name="TValue">The type of the values in the dictionary.</typeparam>
        /// <param name="dictionary">The dictionary to synchronize.</param>
        /// <returns>A synchronized dictiononary wrapping the specified dictionary.</returns>
        public static SynchronizedDictionary<TKey, TValue> AsSynchronized<TKey, TValue>(this IDictionary<TKey, TValue> dictionary)
        {
            Require.ThatArgument(dictionary).Named("dictionary").IsNotNull();

            var synchronizationManager = new ReaderWriterLockSlimAdapter(new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion));

            return new SynchronizedDictionary<TKey, TValue>(
                dictionary,
                synchronizationManager);
        }

        /// <summary>
        /// Concatenates all the strings in the specified sequence.
        /// </summary>
        /// <param name="values">The values to concatenate.</param>
        /// <returns>The concatenated sequence.</returns>
        public static string Concatenate(this IEnumerable<string> values)
        {
            return values.Concatenate(string.Empty);
        }

        /// <summary>
        /// Concatenates all the strings in the specified sequence, separated by
        /// the specified separator.
        /// </summary>
        /// <param name="values">The values to concatenate.</param>
        /// <param name="separator">A string that will be inserted between all the values
        /// in the resulting string.</param>
        /// <returns>The concatenated sequence, separated by
        /// the specified separator.</returns>
        public static string Concatenate(this IEnumerable<string> values, string separator)
        {
            return values.Concatenate(separator, string.Empty, string.Empty);
        }

        /// <summary>
        /// Concatenates all the strings in the specified sequence, separated by
        /// the specified separator, prefixed by the value specified in <paramref name="prefix" /> and
        /// suffixed by the value specified in <paramref name="suffix"/>.
        /// </summary>
        /// <param name="values">The values to concatenate.</param>
        /// <param name="separator">A string that will be inserted between all the values
        /// in the resulting string.</param>
        /// <param name="prefix">A string that will be the start of the result string.</param>
        /// <param name="suffix">A string that will be the end of the result string.</param>
        /// <returns>The concatenated sequence, separated by
        /// the specified separator, prefixed by the value specified in <paramref name="prefix" /> and
        /// suffixed by the value specified in <paramref name="suffix"/>.</returns>
        public static string Concatenate(this IEnumerable<string> values, string separator, string prefix, string suffix)
        {
            Require.ThatArgument(values).Named("values").IsNotNull();
            Require.ThatArgument(separator).Named("separator").IsNotNull();
            Require.ThatArgument(prefix).Named("prefix").IsNotNull();
            Require.ThatArgument(suffix).Named("suffix").IsNotNull();

            var result = new StringBuilder();

            result.Append(prefix);

            foreach (var value in values)
            {
                if (result.Length > prefix.Length)
                {
                    result.Append(separator);
                }

                result.Append(value);
            }
            
            result.Append(suffix);

            return result.ToString();
        }

        /// <summary>
        /// Weaves the two specified sequences together, taking the first element
        /// from the first sequence followed by the first element of the other sequence, then
        /// the second of the first sequence followed by the second of the other sequence and so on.
        /// </summary>
        /// <typeparam name="T">The type of values in the sequences.</typeparam>
        /// <param name="collection">The first sequence.</param>
        /// <param name="other">The other sequence.</param>
        /// <returns>A new sequence consisting of the two sequences weaved together.</returns>
        public static IEnumerable<T> Weave<T>(this IEnumerable<T> collection, IEnumerable<T> other)
        {
            Require.ThatArgument(collection).Named("collection").IsNotNull();
            Require.ThatArgument(other).Named("other").IsNotNull();

            return new WeavingEnumerable<T>() { First = collection, Second = other };
        }

        /// <summary>
        /// Converts the sequence to a list. The actual enumeration of the sequence
        /// is postponed until the list is accessed for the first time. The sequence
        /// will be enumerated no more than once.
        /// </summary>
        /// <typeparam name="T">The type of items in the sequence.</typeparam>
        /// <param name="sequence">The sequence to convert to a list.</param>
        /// <returns>A list.</returns>
        public static IList<T> LazyToList<T>(this IEnumerable<T> sequence)
        {
            Require.ThatArgument(sequence, "sequence").IsNotNull();

            return new LazyLoadedList<T>(sequence);
        }

        private class WeavingEnumerable<T>
            : IEnumerable<T>
        {
            public IEnumerable<T> First;
            public IEnumerable<T> Second;

            public IEnumerator<T> GetEnumerator()
            {
                return new WeavingEnumerator() {
                    NextEnumerator  = First.GetEnumerator(),
                    CurrentEnumerator = Second.GetEnumerator()
                };
            }

            System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
            {
                return this.GetEnumerator();
            }

            private class WeavingEnumerator
                : IEnumerator<T>
            {
                public IEnumerator<T> CurrentEnumerator;
                public IEnumerator<T> NextEnumerator;

                public T Current
                {
                    get
                    {
                        return this.CurrentEnumerator.Current;
                    }
                }

                public void Dispose()
                {
                    CurrentEnumerator.Dispose();
                    NextEnumerator.Dispose();
                }

                object System.Collections.IEnumerator.Current
                {
                    get
                    {
                        return this.Current;
                    }
                }

                public bool MoveNext()
                {
                    this.SwitchCurrentEnumerator();

                    if (CurrentEnumerator.MoveNext() || this.SwitchCurrentEnumerator().MoveNext())
                    {
                        return true;
                    }

                    return false;
                }

                private IEnumerator<T> SwitchCurrentEnumerator()
                {
                    var current = this.CurrentEnumerator;
                    this.CurrentEnumerator = this.NextEnumerator;
                    this.NextEnumerator = current;

                    return this.CurrentEnumerator;
                }

                public void Reset()
                {
                    throw new NotSupportedException();
                }

            }
        }
    }
}

